/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.controller.admin;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.zkoss.chart.*;
import org.zkoss.chart.plotOptions.GaugePlotOptions;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Label;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by shiny on 2018-09-20.
 */
public class JvmController extends SelectorComposer<Component> {

    @Wire("label#vmVersion")
    private Label vmVersion;

    @Wire("label#vmVendor")
    private Label vmVendor;

    @Wire("label#vmArgs")
    private Label vmArgs;

    @Wire("label#osName")
    private Label osName;

    @Wire("label#osArch")
    private Label osArch;

    @Wire("label#processors")
    private Label processors;

    @Wire("label#pid")
    private Label pid;

    @Wire("label#totalMemory")
    private Label totalMemory;

    @Wire("label#startTime")
    private Label startTime;

    @Wire("label#upTime")
    private Label upTime;

    @Wire("label#vmCommand")
    private Label vmCommand;

    @Wire("label#totalThread")
    private Label totalThread;

    @Wire("charts#gaugeChart")
    private Charts gaugeChart;

    @Wire("charts#cpuChart")
    private Charts cpuChart;

    @Wire("charts#memoryChart")
    private Charts memoryChart;

    private long maxMemoryUsed;

    private double maxCpuUsed;

    private com.sun.management.OperatingSystemMXBean osm;

    private MemoryMXBean mmb;

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        osm = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        mmb = ManagementFactory.getMemoryMXBean();
        RuntimeMXBean rmb = ManagementFactory.getRuntimeMXBean();
        vmVersion.setValue(rmb.getVmName() + " " + rmb.getSpecVersion() + "-" + rmb.getVmVersion());
        vmVersion.setTooltiptext(vmVersion.getValue());
        vmVendor.setValue(rmb.getVmVendor());
        vmVendor.setTooltiptext(vmVendor.getValue());
        vmArgs.setValue(StringUtils.substringBetween(Arrays.toString(rmb.getInputArguments().toArray()), "[", "]"));
        vmArgs.setTooltiptext(vmArgs.getValue());

        osName.setValue(osm.getName());
        osName.setTooltiptext(osName.getValue());
        osArch.setValue(osm.getArch());
        osArch.setTooltiptext(osArch.getValue());
        processors.setValue(osm.getAvailableProcessors() + Labels.getLabel("admin.jvm.code"));
        pid.setValue(rmb.getName().split("@")[0]);
        totalMemory.setValue(String.format("%.2fG", osm.getTotalPhysicalMemorySize() / (Math.pow(1024.0, 3.0))));
        startTime.setValue(DateFormatUtils.format(rmb.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
        upTime.setValue(calculateInterval(rmb.getUptime()));
        vmCommand.setValue(System.getProperty("sun.java.command"));
        vmCommand.setTooltiptext(vmCommand.getValue());
        totalThread.setValue(ManagementFactory.getThreadMXBean().getThreadCount() + "");

        LinearGradient linearGradient = new LinearGradient(0, 0, 0, 1);
        linearGradient.addStop(0, "transparent");
        linearGradient.addStop(0.3, "transparent");
        linearGradient.addStop(1, "transparent");
        gaugeChart.setPlotBackgroundColor(linearGradient);
        gaugeChart.getExporting().setEnabled(false);
        Pane pane1 = gaugeChart.getPane();
        pane1.setStartAngle(-55);
        pane1.setEndAngle(55);
        pane1.setBackground(new ArrayList<PaneBackground>());
        pane1.setCenter("25%", "145%");
        pane1.setSize(400);

        Pane pane2 = gaugeChart.getPane(1);
        pane2.setStartAngle(-55);
        pane2.setEndAngle(55);
        pane2.setBackground(new ArrayList<PaneBackground>());

        pane2.setCenter("75%", "145%");
        pane2.setSize(400);

        long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
        if (heapSize > 999) {
            heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
        } else {
            heapSize = 1024;
        }
        YAxis yAxis1 = gaugeChart.getYAxis();
        yAxis1.setMin(0);
        yAxis1.setMax(heapSize);
        yAxis1.setMinorTickPosition("outside");
        yAxis1.setTickPosition("outside");
        List<Number> positions = new ArrayList<>();
        int increment = new BigDecimal(heapSize / 10).intValue();
        if (heapSize > 999)
            increment = increment - (increment % 100);
        else
            increment = increment - (increment % 50);
        for (double tick = 0; tick <= heapSize; tick += increment)
            positions.add(tick);
        yAxis1.setTickPositions(positions);
        yAxis1.getLabels().setRotation("auto");
        yAxis1.getLabels().setDistance(20);

        maxMemoryUsed = mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024;
        maxCpuUsed = osm.getProcessCpuLoad() * 100;
        yAxis1.setPane(0);
        yAxis1.setTitle("<strong>" + Labels.getLabel("admin.jvm.memory") + "(M)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + maxMemoryUsed + "M  " + Labels.getLabel("admin.jvm.max") + ":" + maxMemoryUsed + "M");
        yAxis1.getTitle().setY(-40);

        YAxis yAxis2 = gaugeChart.getYAxis(1);
        yAxis2.setMin(0);
        yAxis2.setMax(100);
        yAxis2.setMinorTickPosition("outside");
        yAxis2.setTickPosition("outside");
        yAxis2.getLabels().setRotation("auto");
        List<Number> ps = new ArrayList<>();
        for (int i = 0; i <= 10; i++)
            ps.add(i * 10);
        yAxis2.setTickPositions(ps);
        yAxis2.getLabels().setDistance(20);

        PlotBand plotBand = new PlotBand();
        plotBand.setFrom(70);
        plotBand.setTo(100);
        plotBand.setColor("#C02316");
        plotBand.setInnerRadius("100%");
        plotBand.setOuterRadius("105%");
        yAxis2.addPlotBand(plotBand);

        yAxis2.setPane(1);
        yAxis2.setTitle("<strong>CPU(%)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + String.format("%.2f", maxCpuUsed) + "%  " + Labels.getLabel("admin.jvm.max") + ":" + String.format("%.2f", maxCpuUsed) + "%");
        yAxis2.getTitle().setY(-40);

        GaugePlotOptions plotOptions = gaugeChart.getPlotOptions().getGauge();
        plotOptions.getDataLabels().setEnabled(false);
        plotOptions.getDial().setRadius("100%");

        Series series1 = gaugeChart.getSeries();
        series1.setData(0);
        series1.setYAxis(0);

        Series series2 = gaugeChart.getSeries(1);
        series2.setData(0);
        series2.setYAxis(1);

        //cpu使用率
        Options options = new Options();
        options.getGlobal().setUseUTC(false);
        cpuChart.setOptions(options);
        cpuChart.setAnimation(true);

        cpuChart.getXAxis().setType("datetime");
        cpuChart.getXAxis().setTickPixelInterval(150);
        cpuChart.getYAxis().setMin(0);
        cpuChart.getYAxis().setMax(100);
        cpuChart.getYAxis().setTitle("");
        cpuChart.getYAxis().setTickInterval(20);
        cpuChart.getYAxis().getLabels().setFormat("{value}%");
        PlotLine plotLine = new PlotLine();
        plotLine.setValue(0);
        plotLine.setWidth(1);
        plotLine.setColor("#808080");
        cpuChart.getYAxis().addPlotLine(plotLine);
        cpuChart.getTooltip().setEnabled(false);

        cpuChart.getExporting().setEnabled(false);
        Legend legend = cpuChart.getLegend();
        legend.setLayout("vertical");
        legend.setAlign("right");
        legend.setVerticalAlign("top");
        legend.setY(0);
        legend.setFloating(true);

        Series jvmSeries = cpuChart.getSeries();
        jvmSeries.getMarker().setEnabled(false);
        jvmSeries.setName("JVM CPU Usage");
        jvmSeries.setColor("#32CD32");
        Series systemSeries = new Series();
        systemSeries.getMarker().setEnabled(false);
        systemSeries.setName("Machine CPU Usage");
        systemSeries.setColor("#436EEE");
        cpuChart.addSeries(systemSeries);
        for (int i = -19; i < 0; i++) {
            jvmSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            systemSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
        }
        jvmSeries.addPoint(new Point(System.currentTimeMillis(), maxCpuUsed));
        systemSeries.addPoint(new Point(System.currentTimeMillis(), osm.getSystemCpuLoad() * 100));

        //memory使用率
        memoryChart.setAnimation(true);

        memoryChart.getXAxis().setType("datetime");
        memoryChart.getXAxis().setTickPixelInterval(150);
        memoryChart.getYAxis().setMin(0);
        memoryChart.getYAxis().setMax(heapSize);
        memoryChart.getYAxis().setTitle("");
        memoryChart.getYAxis().setTickInterval(10);
        memoryChart.getYAxis().getLabels().setFormat("{value}M");
        //memoryChart.getYAxis().getLabels().setFormatter(new JavaScriptValue(""));
        memoryChart.getTooltip().setEnabled(false);

        memoryChart.getExporting().setEnabled(false);
        legend = memoryChart.getLegend();
        legend.setLayout("vertical");
        legend.setAlign("right");
        legend.setVerticalAlign("top");
        legend.setY(0);
        legend.setFloating(true);

        Series committedSeries = memoryChart.getSeries();
        committedSeries.getMarker().setEnabled(false);
        committedSeries.setName("Committed Java Heap");
        Series maxSeries = new Series();
        maxSeries.getMarker().setEnabled(false);
        maxSeries.setName("Maximum Java Heap");
        memoryChart.addSeries(maxSeries);
        Series usedJavaSeries = new Series();
        usedJavaSeries.getMarker().setEnabled(false);
        usedJavaSeries.setName("Used Java Heap Memory");
        memoryChart.addSeries(usedJavaSeries);
        Series usedPhysicalSeries = new Series();
        usedPhysicalSeries.getMarker().setEnabled(false);
        usedPhysicalSeries.setName("Used Physical Memory");
        memoryChart.addSeries(usedPhysicalSeries);
        for (int i = -19; i < 0; i++) {
            committedSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            maxSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            usedJavaSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            usedPhysicalSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
        }
        committedSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getCommitted() / 1024 / 1024));
        maxSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getMax() / 1024 / 1024));
        usedJavaSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024));
        usedPhysicalSeries.addPoint(new Point(System.currentTimeMillis(), (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024));

        committedSeries.hide();
        maxSeries.hide();
        usedPhysicalSeries.hide();

    }

    @Listen("onPlotHide = #memoryChart")
    public void hideSeries(ChartsEvent event) {
        long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
        if (heapSize > 999) {
            heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
        } else {
            heapSize = 1024;
        }
        event.getSeries().setVisible(false);
        if (!memoryChart.getSeries(3).isVisible()) {
            memoryChart.getYAxis().setMax(heapSize);
            memoryChart.getYAxis().setTickInterval(10);
        }
    }

    @Listen("onPlotShow = #memoryChart")
    public void showSeries(ChartsEvent event) {
        long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
        if (heapSize > 999) {
            heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
        } else {
            heapSize = 1024;
        }
        event.getSeries().setVisible(true);
        if (event.getSeriesIndex() == 3) {//Used Physical Memory
            long usedPhysical = (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024;
            if (heapSize < usedPhysical) {
                memoryChart.getYAxis().setMax(usedPhysical);
                memoryChart.getYAxis().setTickInterval(20);
            }
        } else if (!memoryChart.getSeries(3).isVisible()) {
            memoryChart.getYAxis().setMax(heapSize);
            memoryChart.getYAxis().setTickInterval(10);
        }
    }

    @Listen("onTimer = #timer")
    public void updateData() {
        Point left = gaugeChart.getSeries().getPoint(0),
                right = gaugeChart.getSeries(1).getPoint(0);
        long memoryUsed = mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024;
        if (memoryUsed > maxMemoryUsed)
            maxMemoryUsed = memoryUsed;
        left.update(memoryUsed);
        double cpuUsed = osm.getProcessCpuLoad() * 100;
        if (cpuUsed > maxCpuUsed)
            maxCpuUsed = cpuUsed;
        right.update(cpuUsed);
        YAxis yAxis1 = gaugeChart.getYAxis();
        yAxis1.setTitle("<strong>" + Labels.getLabel("admin.jvm.memory") + "(M)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + memoryUsed + "M  " + Labels.getLabel("admin.jvm.max") + ":" + maxMemoryUsed + "M");
        YAxis yAxis2 = gaugeChart.getYAxis(1);
        yAxis2.setTitle("<strong>CPU(%)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + String.format("%.2f", cpuUsed) + "%  " + Labels.getLabel("admin.jvm.max") + ":" + String.format("%.2f", maxCpuUsed) + "%");

        cpuChart.getSeries().addPoint(new Point(System.currentTimeMillis(), cpuUsed), true, true, true);
        cpuChart.getSeries(1).addPoint(new Point(System.currentTimeMillis(), osm.getSystemCpuLoad() * 100), true, true, true);


        memoryChart.getSeries(0).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getCommitted() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(1).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getMax() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(2).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(3).addPoint(new Point(System.currentTimeMillis(), (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024), true, true, true);
    }

    private String calculateInterval(long uptime) {
        if (uptime == 0) {
            return "0" + Labels.getLabel("admin.jvm.minute");
        }
        StringBuilder str = new StringBuilder();
        long p = Math.abs(uptime);
        long day = p / (24 * 3600000);
        if (day > 0) {
            str.append(day).append(Labels.getLabel("admin.jvm.day"));
        }
        p = p % (24 * 3600000);
        long hour = p / (3600000);
        if (hour > 0) {
            str.append(hour).append(Labels.getLabel("admin.jvm.hour"));
        }
        p = p % (3600000);
        long minute = p / (60000);
        if (minute > 0) {
            str.append(minute).append(Labels.getLabel("admin.jvm.minute"));
        }
        p = p % (60000);
        long second = p / (1000);
        if (second > 0) {
            str.append(second).append(Labels.getLabel("admin.jvm.second"));
        }
        return str.toString();
    }
}
